
##################### SECTION 1: Basic OOP, Ruby libraries, introspection #######################################

## Section 1: question 1
class Fixnum
  def squared
    return self*self
  end
end
# test as: puts 9.squared

## section1 : question 2
#Answer: We can say Time.parse("1:00 pm") because parse is a class method in Time class.We can't say "1:00 pm".parse because there is no instance method 'parse' defined for string object. We can call "1.00 pm".parse if we define an instance method 'parse' in class String.  
#

require 'time'

class Movie
  attr_reader :name
  attr_reader :showtime
  
  include Enumerable # for section 3

  #The initialize method has been modified according to Section1-question3. 
  def initialize(name,showtime)
    @name = name
    if showtime.kind_of?Time
      @showtime = showtime
    else
      if showtime.kind_of?String
         @showtime=Time.parse(showtime)
      else
        raise ArgumentError, 'Invalid Argument found. (string or Time object)'
      end
    end
  end

  #The name_and_showtime method -  Section1-question4. 
  def name_and_showtime
    if @showtime > Time.now
      puts "#{@name} :Next show at: #{@showtime.strftime("%I:%M %p")}"
    elsif @showtime < Time.now
      puts "#{@name} :Show already started at: #{@showtime.strftime("%I:%M %p")}"
    else
      puts "The show is starting now"
    end
  end

###########################  SECTION 2: Modules  ##############################################
  #The starship operator method - section2-question1. 
  def <=>(obj)
    self.showtime <=> obj.showtime
  end
  #Mixing comparable module - section2-question2.
  include Comparable
end

#Mixing comparable module - section2-question3.
#puts movies.sort()[0].name
#The sort method works because we included comparable module in the class (which has sort method defined). The sort method requires understanding the order among objects which is defined by startship operator method. Comparable uses this method to give <, >, <=, >= and == operators methods which are used by sort.



movies = []
movies << Movie.new("Orphan", Time.parse("1:00 pm"))
movies << Movie.new("Orphan", Time.parse("4:05 pm"))
movies << Movie.new("Orphan", Time.parse("11:50 pm"))
movies << Movie.new("GI Joe", Time.parse("1:05 pm"))
movies << Movie.new("GI Joe", Time.parse("4:30 pm"))
movies << Movie.new("GI Joe", Time.parse("6:50 pm"))
movies << Movie.new("Rocky Horror", Time.parse("12:00 am"))
movies << Movie.new("District 9", Time.parse("11:00 am"))
movies << Movie.new("District 9", Time.parse("3:00 pm"))

##################### SECTION 3: Higher-order functions & functional programming #######################################
# Question 1
module Enumerable
  def group_by
    temp_hash={}
    self.each do |x|
      y=yield(x)
      temp_hash[y]=[] if !temp_hash.has_key?y
      temp_hash[y] << x
    end
    temp_hash
  end
end
# Question 2 : it works for group_by method below
#puts movies.group_by {|m| m.name}


# Question 3.1

puts "Question 3.1 \n #{movies.select {|m| m.showtime > Time.parse("5:00pm")}}"
puts "Question 3.2 \n #{movies.select {|m| m.showtime > Time.parse("5:00pm")}.count}"
puts "Question 3.3 \n #{movies.select {|m| m.showtime > Time.parse("5:00pm")}.collect{|m| m.name}}"

##################### SECTION 4: Iterators ######################################
# Question 1 each_even iterator
module Enumerable
  def each_even
    self.each {|x| yield(x) if x%2==0}
  end
end

# Question 2 each_grouped_by_first_letter
module Enumerable
  def each_grouped_by_first_letter
    self.collect{|x| x[0]}.uniq.each do |t|
      yield(t,self.select{|y| y[0]==t})
    end
  end
end

##################### SECTION 5: Extending classes ######################################
#
class IndifferentHash < Hash
  def [](arg)
    return super(arg.to_sym) if super(arg.to_sym)
    return super(arg.to_s) if super(arg.to_s)
  end
end

##################### SECTION 6: Language extensions for convenience ######################################
#
#Question 1: 
#empty method is not defined for a nil object. Also empty method does not consider whitespaces as empty and hence returns 'false'.
#
#Question 4: Blank method implementation that can be called on any receiver. defined in class 'Object'
#As Object is the root class for all ruby objects, this method can be called for any receiver.
class Object
  def blank?
    return true if self.nil?
    return true if self.empty?
  end
end

#Question 3: Although as described in above question, the string will also respond to 'blank?' method as String is a subclass of Object. For 'blank?' to only respond for String, we will include the blank? method described above in the String class. 

#Question 4: I do not understand the difference between question 2 and question 4 (in section 6). Does 'called on any receiver' and 'any object should respond to' mean different things? To me they look the same thing. Anyway here is the implementation of 'blank?' method according to the requirements of question4. (again included in 'Object' so that any object can respond to it.
class Object
  def blank?
    if self.respond_to?'empty?'
      return self.empty?
    else
      if self.nil?
        return true
      else
        return false
      end
    end
  end
end
